package audit

import (
	"os"
	"path"
	"time"

	"github.com/oklog/ulid/v2"
	"go.uber.org/zap"

	"codeberg.org/vnpower/pixivfe/v2/config"
	"codeberg.org/vnpower/pixivfe/v2/i18n"
)

// RecordedRequestSpans stores a slice of requestSpan objects.
var RecordedRequestSpans = []Span{}

// LogAndRecord logs the given requestSpan and optionally records it.
func (a *Auditor) LogAndRecord(requestSpan Span) {
	duration := float64(Duration(requestSpan)) / float64(time.Second)

	// Determine the log level based on the presence of an error
	if requestSpan.Error() != nil {
		a.Logger.Error("Request",
			zap.String("component", requestSpan.Component()),
			zap.Any("action", requestSpan.Action()),
			zap.Any("outcome", requestSpan.Outcome()),
			zap.Float64("duration", duration),
		)
	} else {
		a.Logger.Info("Request",
			zap.String("component", requestSpan.Component()),
			zap.Any("action", requestSpan.Action()),
			zap.Any("outcome", requestSpan.Outcome()),
			zap.Float64("duration", duration),
		)
	}

	// If MaxRecordedCount is set, manage the RecordedRequestSpans slice
	if MaxRecordedCount != 0 {
		// Remove the oldest span if we're at capacity
		if len(RecordedRequestSpans)+1 > MaxRecordedCount {
			RecordedRequestSpans = RecordedRequestSpans[1:]
		}
		// Append the new span
		RecordedRequestSpans = append(RecordedRequestSpans, requestSpan)
	}
}

// LogServerRoundTrip logs and records a ServerRequestSpan.
func (a *Auditor) LogServerRoundTrip(requestSpan ServerRequestSpan) {
	a.LogAndRecord(requestSpan)
}

// LogAPIRoundTrip logs and records an APIRequestSpan.
func (a *Auditor) LogAPIRoundTrip(requestSpan APIRequestSpan) {
	// Save response body to file if enabled and conditions are met
	if requestSpan.Response != nil && requestSpan.Body != "" && optionSaveResponse {
		var err error
		requestSpan.ResponseFilename, err = a.writeResponseBodyToFile(requestSpan.Body)
		if err != nil {
			a.Logger.Error("Failed to save response to file",
				zap.Error(err),
				zap.String("requestId", requestSpan.RequestId),
			)
		}
	}

	a.LogAndRecord(requestSpan)
}

// writeResponseBodyToFile saves the given response body to a file.
func (a *Auditor) writeResponseBodyToFile(body string) (string, error) {
	// Generate a unique ID using ULID
	id := ulid.Make().String()

	// Create a filename using the last 6 characters of the ID
	filename := path.Join(config.GlobalConfig.ResponseSaveLocation, id[len(id)-6:])

	// Write the body to the file with read/write permissions for the owner only
	err := os.WriteFile(filename, []byte(body), 0o600)
	if err != nil {
		return "", i18n.Errorf("failed to write response body to file %s: %w", filename, err)
	}

	return filename, nil
}
